﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DotNetStar.TheKing.Domain;
using DotNetStar.TheKing.Infrastructure;
using Microsoft.Extensions.Caching.Memory;
using SDDP;
using NinjaNye.SearchExtensions;

namespace DotNetStar.TheKing.Services
{
    using Microsoft.EntityFrameworkCore;
    using Models;
    using System.Linq.Expressions;
    using System.Text.RegularExpressions;


    /// <summary>
    /// 内容服务。
    /// </summary>
    public partial class ContentService : ServiceBase
    {
        #region 初始化
        IStorage<Content,long> _contentStorage;
        IStorage<ContentComment,Guid> _contentCommentStorage;
        IStorage<Category, int> _categoryStorage;
        IStorage<User, long> _userStorage;
        IStorage<UserOperationHistory, Guid> _userOperationHistoryStorage;
        
        IStorage<ContentTag, Guid> _contentTagStorage;
        IStorage<Tag, int> _tagStorage;
        IStorage<UserContentPayHistory, Guid> _userContentPayStorage;
        IStorage<ContentInviteUser, Guid> _contentInviteUserStorage;


        IEventBus _eventBus;

        UserService _userService;
        TagService _tagService;

        public ContentService(IMemoryCache memoryCache,
            IStorage<Content, long> contentStorage,
            IStorage<ContentComment, Guid> contentCommentStorage,
            IStorage<User, long> userStorage,
            IStorage<UserOperationHistory, Guid> userOperationHistoryStorage,
            IStorage<Category,int> categoryStorage,
        IStorage<ContentTag, Guid> contentTagStorage,
        IStorage<Tag, int> tagStorage,
        IStorage<UserContentPayHistory, Guid> userContentPayStorage,

        IStorage<ContentInviteUser, Guid> contentInviteUserStorage,
        UserService userService,
            PointService pointService,
            TagService tagService,
            IEventBus eventBus
            ) 
            : base(memoryCache)
        {
            _contentStorage = contentStorage;
            _contentCommentStorage = contentCommentStorage;
            _userOperationHistoryStorage = userOperationHistoryStorage;
            _userStorage = userStorage;            
            _contentTagStorage = contentTagStorage;
            _tagStorage = tagStorage;
            _categoryStorage = categoryStorage;
            _userContentPayStorage = userContentPayStorage;
            _tagService = tagService;
            _userService = userService;
            _contentInviteUserStorage = contentInviteUserStorage;

            _eventBus = eventBus;
        }
        #endregion

        #region Content

        #region GetContentsAsync
        /// <summary>
        /// 获取帖子列表
        /// </summary>
        /// <param name="contentPageQuery">带分页的实例</param>
        /// <returns></returns>
        public Task<PagedCollection<Content>> GetContentsAsync(ContentPageQuery contentPageQuery)
        => Task.Run(() =>
             GetContentQueable(contentPageQuery).OrderBy(m => m.OrderNo)
              .OrderByDescending(m => m.PostedTime)
              .Paged(contentPageQuery.Page, contentPageQuery.Size)
              .AsPageCollection());
        #endregion

        #region GetContentQueable
        public IQueryable<Content> GetContentQueable(IContentQuery contentQuery)
        {
            IQueryable<Content> query = (from c in _contentStorage.Query()
                                         join u in _userStorage.Query() on c.AuthorId equals u.Id
                                         join category in _categoryStorage.Query() on c.CategoryId equals category.Id into tempCategory
                                         from leftJoinCategory in tempCategory.DefaultIfEmpty()
                                         select new Content
                                         {
                                             Subject = c.Subject,
                                             Author = u,
                                             Body = c.Body,
                                             AuthorId = c.AuthorId,
                                             IsOriginal = c.IsOriginal,
                                             IsSecret = c.IsSecret,
                                             LikeNum = c.LikeNum,
                                             OrderNo = c.OrderNo,
                                             Status = c.Status,
                                             PostedTime = c.PostedTime,
                                             VisitNum = c.VisitNum,
                                             Url = c.Url,
                                             Category = leftJoinCategory,
                                             CategoryId = c.CategoryId,
                                             Channel = c.Channel,
                                             Cost = c.Cost,
                                             Id = c.Id,
                                             BestCommentId = c.BestCommentId,
                                             IsLockComment = c.IsLockComment,
                                             OnlyInviteUser = c.OnlyInviteUser,
                                             InvitedUsers = (from ci in _contentInviteUserStorage.Query()
                                                             join u in _userStorage.Query() on ci.UserId equals u.Id
                                                             where ci.ContentId == c.Id
                                                             select u
                                                          ).ToList(),
                                             CommentCount = _contentCommentStorage.Query().Count(m => m.ContentId == c.Id),
                                             ContentTags = (from ct in _contentTagStorage.Query()
                                                            join t in _tagStorage.Query() on ct.TagId equals t.Id
                                                            where ct.ContentId == c.Id
                                                            select t
                                                          ).ToList()
                                         }
                            )
                 ;

            if (contentQuery.Channel.HasValue)
            {
                query = query.Where(m => m.Channel == contentQuery.Channel.Value);
            }

            if (contentQuery.CategoryId.HasValue)
            {
                query = query.Where(m => m.CategoryId == contentQuery.CategoryId.Value);
            }

            if (contentQuery.AuthorUserId.HasValue)
            {
                query = query.Where(m => m.AuthorId == contentQuery.AuthorUserId.Value);
            }

            if (contentQuery is IKeywordQuery keywordQuery && !keywordQuery.Keyword.IsNullOrWhiteSpace())
            {
                var splitKeyword = keywordQuery.Keyword.Split(' ');
                for (int i = 0; i < splitKeyword.Length; i++)
                {
                    splitKeyword[i] = splitKeyword[i].Trim();
                }
                query = query.Search(m => m.Subject).Containing(splitKeyword)
                    .SearchChildren(m => m.ContentTags.Select(c => c.Name));

               // query =query.Where(m=>m.ContentTags.Select(m=>m.Name).c)
            }

            query = query.Where(m => contentQuery.IncludeStatus.Contains(m.Status));

            if(contentQuery is IContentQueryIncludeSecret includeSecret && !includeSecret.IncludeSecret) 
            {
                query = query.Where(m => !m.IsSecret);
            }

            return query;
        }
        #endregion

        #region GetTopContentQueable
        public IQueryable<Content> GetTopContentQueable(IContentQuery contentQuery, int top = 10, int skip = 0)
        => GetContentQueable(contentQuery).Skip(skip).Take(top);
        #endregion

        #region GetContentAsync
        /// <summary>
        /// 以异步方式获取内容详情。
        /// </summary>
        /// <param name="contentId"></param>
        /// <param name="increaseVisitNum">是否增加点击率。true 时，若找不到帖子对象，会抛出 <see cref="ContentNotFoundException"/> 异常。</param>
        /// <returns></returns>
        /// <exception cref="ContentNotFoundException">内容是null</exception>
        public async Task<Domain.Content> GetContentAsync(long contentId,bool increaseVisitNum=true)
        {
            var content = GetContentQueable(new ContentQuery
            {
                 IncludeStatus=new ContentStatus[]
                 {
                      ContentStatus.Auditing,
                       ContentStatus.Draft,
                        ContentStatus.Normal,                         
                 },
                  IncludeSecret=true,
            }).SingleOrDefault(m => m.Id == contentId);
                ;
            if (content == null && increaseVisitNum)
            {
                throw new ContentNotFoundException(contentId);
            }

            if (increaseVisitNum)
            {
                var entity = await _contentStorage.FindAsync(contentId);
                entity.IncreaseVisitNum();
                await _contentStorage.Context.CommitAsync();

                content.VisitNum = entity.VisitNum;                
            }

            content.ContentTags = (from c in _contentTagStorage.Query()
                                   join t in _tagStorage.Query() on c.TagId equals t.Id
                                   where c.ContentId == contentId
                                   select t
                                  ).ToList();

            return content;
        }
        #endregion

        #region SaveContentAsync
        /// <summary>
        /// 保存内容。Id >0 视为编辑，仅允许编辑标题和正文。
        /// </summary>
        /// <param name="model"></param>
        /// <param name="userId">作者Id。编辑时为 null。</param>
        /// <returns></returns>
        public async Task<ExecutedResult<Domain.Content>> SaveContentAsync(EditContentViewModel model,long userId)
        {
            try
            {
                if (model == null)
                {
                    throw new ArgumentNullException(nameof(model));
                }

                

                if (!model.IsDraft)
                {
                    var errors = model.GetErrors();
                    if (errors.Any())
                    {
                        return ExecutedResult<Domain.Content>.Failed(errors.ToArray());
                    }
                }

                bool isNew = model.Id == 0;


                var user = await _userService.GetByIdAsync(userId);
                if (user == null)
                {
                    return ExecutedResult<Domain.Content>.Failed("当前用户不存在");
                }

                var content = new Content();

                if (!isNew)
                {
                    content = await _contentStorage.GetByIdAsync(model.Id);

                    if(content.IsLockComment && content.IsAsk)
                    {
                        return ExecutedResult<Content>.Failed("当前问题已经有了答案，不可再操作");
                    }
                }
                else
                {
                    content.SetAuthor(user);
                    content.PostedTime = DateTime.Now;//to do 管理员可以设置发布时间
                    content.Channel = model.Channel;
                }


                var originalCost = content.Cost;//提交前的数据，目前仅针对问答作比较使用

                content.Subject = model.Subject;
                content.Body = model.Body;
                content.Url = model.Url;
                content.IsOriginal = model.IsOriginal;
                content.CategoryId = model.CategoryId;
                content.IsSecret = model.IsSecret;
                content.OrderNo = model.OrderNo;
                content.OnlyInviteUser = model.OnlyInvateUser;
                
                if (model.IsAsk )
                {
                    if (!model.Cost.HasValue || model.Cost.Value < 1)
                    {
                        return ExecutedResult<Domain.Content>.Failed("悬赏的金币数量至少为1个");
                    }
                    if (model.Cost.Value > user.TradingPoints)
                    {
                        return ExecutedResult<Content>.Failed($"您的金币不足（剩余：{user.TradingPoints}）");
                    }
                }

                content.SetCost(model.Cost);

                //if (!model.IsAsk)
                //{
                //    if (model.IsLockComment)
                //    {
                //        content.LockComment();
                //    }
                //    else
                //    {
                //        content.UnlockComment();
                //    }
                //}

                if (model.IsDraft)
                {
                    content.SaveDraft();
                }
                else
                {
                    content.Audited();
                }

                if (isNew)
                {
                    _contentStorage.Add(content);
                }

                await _contentStorage.Context.CommitAsync();

                if (model.Tags != null && model.Tags.Any())
                {
                    var tagIds = await _tagService.SaveTagAsync(model.Tags.ToArray());

                    foreach (var item in _contentTagStorage.Query(m => m.ContentId == content.Id).ToList())
                    {
                        _contentTagStorage.Remove(item);
                    } 

                    foreach (var item in tagIds)
                    {
                        _contentTagStorage.Add(new ContentTag
                        {
                            TagId = item,
                            ContentId = content.Id
                        });
                        await _contentTagStorage.Context.CommitAsync();
                    }
                }


                UserOperations operations = UserOperations.None;
                var pointStrategy = PointStrategyTypes.PostContent;
                switch (model.Channel)
                {
                    case Channels.Content:
                        operations = isNew ? UserOperations.CreateContent : UserOperations.EditContent;
                        pointStrategy = PointStrategyTypes.PostContent;
                        break;
                    case Channels.Ask:
                        operations = isNew ? UserOperations.CreateAsk : UserOperations.EditAsk;
                        break;
                    case Channels.Code:
                        operations = isNew ? UserOperations.CreateCode : UserOperations.EditCode;
                        pointStrategy = PointStrategyTypes.PostCode;
                        break;
                    case Channels.Video:
                        operations = isNew ? UserOperations.CreateVideo : UserOperations.EditVideo;
                        pointStrategy = PointStrategyTypes.PostVideo;
                        break;
                    default:
                        break;
                }

                if (model.IsAsk)//邀请回答
                {

                    _contentInviteUserStorage.Query(m => m.ContentId == content.Id).ToList()
                        .ForEach(item =>
                        {
                            _contentInviteUserStorage.Remove(item);
                        });


                    //邀请用户
                    foreach (var item in model.InviteUserIds)
                    {
                        var invitedUser = await _userService.GetByIdAsync(item);
                        if (invitedUser != null && !invitedUser.NoInvite)
                        {

                            if (!invitedUser.InviteCost.HasValue)
                            {
                                AddInviteUser();
                            }
                            else if (invitedUser.InviteCost.HasValue && user.TradingPoints - invitedUser.InviteCost > 0)//积分足够
                            {
                                AddInviteUser();
                                _eventBus.Publish(new TradingPointTrasitionEvent(userId, item, invitedUser.InviteCost.Value, "邀请用户"));
                            }
                        }


                        void AddInviteUser()
                        {
                            _contentInviteUserStorage.Add(new ContentInviteUser
                            {
                                UserId = item,
                                ContentId = content.Id
                            });


                            //短信通知
                            _eventBus.Publish(new ContentReplyNotificationEvent(invitedUser, content,
                                    new SmsNotification(invitedUser.Mobile, "SMS_138077935", new
                                    {
                                        author = user.UserName,
                                        name = invitedUser.UserName,
                                        cid = content.Id
                                    })));
                        }
                    }
                    await _contentInviteUserStorage.Context.CommitAsync();
                }

                if (isNew && content.Status == ContentStatus.Normal)//审核过才加积分
                {
                    if (!model.IsAsk)
                    {
                        _eventBus.Publish(new UserPointEvent(userId, pointStrategy));
                    }

                    if (model.IsAsk)
                    {
                        _eventBus.Publish(new TradingPointTrasitionEvent(userId, 0, content.Cost.Value, "提问"));
                    }
                }

                if (!isNew && model.IsAsk)//编辑问答
                {
                    if (originalCost < model.Cost)//如果新设置的金币大于原来编辑前的金币数量，则要再减去相应差价
                    {
                        _eventBus.Publish(new TradingPointTrasitionEvent(userId, 0, model.Cost.Value- originalCost.Value, "提问"));
                    }
                }

                _eventBus.Publish(new UserOperationEvent(operations, content.Id.ToString(), content.AuthorId));


                return ExecutedResult<Domain.Content>.Success(content);
            }catch(Exception ex)
            {
                return ExecutedResult<Domain.Content>.Failed(ex.Message);
            }
        }
        #endregion

        #region DeleteContentAsync
        /// <summary>
        /// 删除指定内容。如果内容不存在，则抛出异常。
        /// </summary>
        /// <param name="id">要删除的id。</param>
        /// <exception cref="NullReferenceException">要删除的 id 不存在。</exception>
        /// <returns></returns>
        public async Task DeleteContentAsync(long id, long userId)
        {
            var content = await GetContentAsync(id);
            if (content == null)
            {
                throw new ContentNotFoundException(id);
            }

            if (content.Author.Id != userId)
            {
                throw new System.UnauthorizedAccessException("您没有删除权限");
            }

            _contentStorage.Remove(_contentStorage.GetById(content.Id));
            await _contentStorage.Context.CommitAsync();
            
            foreach (var item in _contentCommentStorage.Query(m=>m.ContentId==content.Id).ToList())
            {
                _contentCommentStorage.Remove(item);
            }
            await _contentCommentStorage.Context.CommitAsync();

            foreach (var item in _contentTagStorage.Query(m => m.ContentId == content.Id).ToList())
            {
                _contentTagStorage.Remove(item);
            }
            await _contentTagStorage.Context.CommitAsync(); 

            var pointStrategy = PointStrategyTypes.DeleteContent;
            switch (content.Channel)
            {
                case Channels.Content:
                    pointStrategy = PointStrategyTypes.DeleteContent;
                    break;
                case Channels.Ask:
                    break;
                case Channels.Code:
                    pointStrategy = PointStrategyTypes.DeleteCode;
                    break;
                case Channels.Video:
                    pointStrategy = PointStrategyTypes.DeleteVideo;
                    break;
                default:
                    break;
            }

            _eventBus.Publish(new UserPointEvent(content.Author.Id, pointStrategy));

        }
        #endregion

        #region OperateLikeAsync
        /// <summary>
        /// 以异步的方式操作点赞并返回最新的内容。
        /// </summary>
        /// <remarks>
        /// 无记录，赞+1，有记录，不操作
        /// </remarks>
        /// <param name="id">内容Id</param>
        /// <param name="userId">用户Id</param>
        /// <returns>最新的点攒数</returns>
        public async Task<(int likeNum,bool hasLike)> OperateLikeAsync(long id, long userId)
        {
            var content = await _contentStorage.FindAsync(id);
            if (content == null)
            {
                throw new ContentNotFoundException(id);
            }

            var user = await _userService.GetNormalUserByIdAsync(userId);
            if (user == null)
            {
                throw new UserNotNormalException(userId);
            }
            UserOperations operationType = UserOperations.None;
            string objectId = content.Id.ToString();

            //最新赞的记录
            var likeRecord = _userService.GetUserOperations(userId, objectId: objectId).FirstOrDefault(m => m.Type == UserOperations.Like || m.Type == UserOperations.Unlike);
            
            PointStrategyTypes pointStrategyTypes = PointStrategyTypes.Unknow;
            if (likeRecord == null)
            {
                content.IncreaseLike();
                operationType = UserOperations.Like;
                pointStrategyTypes = PointStrategyTypes.Like;
                //else
                //{
                //    content.DecreseLike();
                //    operationType = UserOperations.Unlike;
                //    pointStrategyTypes = PointStrategyTypes.Unlike;
                //}

                _contentStorage.Context.Commit();
                _eventBus.Publish(new UserOperationEvent(operationType, objectId, userId, operationType.GetDescription()));
                _eventBus.Publish(new UserPointEvent(userId, pointStrategyTypes));
            }
            return await IsContentHasLikeAsync(content.Id, userId);
        }
        #endregion

        #region HasLike
        public async Task<(int likeNum,bool hasLike)> IsContentHasLikeAsync(long contentId,long userId)
        {
            var content = await GetContentAsync(contentId);
            var objectId = contentId.ToString();
            var likeRecord = _userService.GetUserOperationHistories(userId)
                .Where(m => m.ObjectId == objectId && new[] { UserOperations.Like, UserOperations.Unlike }.Contains(m.Type))
                .OrderByDescending(m => m.CreatedTime)
                .FirstOrDefault();
            var hasLike = likeRecord != null && likeRecord.Type == UserOperations.Like;

            return (likeNum: content.LikeNum, hasLike: hasLike);
        }
        #endregion

        #region SetAskBestCommentAsync
        /// <summary>
        /// 设置提问的最佳答案。
        /// </summary>
        /// <param name="contentId"></param>
        /// <param name="commentId"></param>
        /// <param name="currentUserId"></param>
        /// <returns></returns>
        public async Task<ExecutedResult<Domain.Content>> SetAskBestCommentAsync(long contentId,Guid commentId, long currentUserId)
        {
            var content = await _contentStorage.FindAsync(contentId);
            if (content == null)
            {
                return ExecutedResult<Content>.Failed("当前内容不存在或已被删除");
            }

            if (content.Channel != Channels.Ask)
            {
                return ExecutedResult<Content>.Failed("类型错误");
            }
            if (currentUserId != content.AuthorId)
            {
                return ExecutedResult<Content>.Failed("你没有权限进行此操作");
            }

            if (content.BestCommentId.HasValue)
            {
                return ExecutedResult<Content>.Failed("该帖子已经有了最佳答案");
            }

            var comment = await GetCommentAsync(commentId);
            if (comment == null)
            {
                return ExecutedResult<Content>.Failed("当前回复不存在或已被删除");
            }

            if (currentUserId == comment.AuthorId)
            {
                return ExecutedResult<Content>.Failed("不允许设置自己的回复");
            }

            content.SetBestComment(commentId);

            await _contentStorage.Context.CommitAsync();

            _eventBus.Publish(new TradingPointTrasitionEvent(0, comment.AuthorId, content.Cost.Value, "最佳答案"));

            //短信通知
            _eventBus.Publish(new ContentReplyNotificationEvent(comment.Author, content,
                    new SmsNotification(comment.Author.Mobile, "SMS_138072636", new
                    {
                        name = comment.Author.UserName,
                        cid = content.Id
                    })));

            return ExecutedResult<Domain.Content>.Success(content);
        }
        #endregion


        #endregion


    }
}
